/**
 * 装饰器引擎定义
 * @version:$Id: DecoratorEngine.js 37 2008-03-02 17:24:56Z dsonet $
 */

/**
 * 脚本装载任务队列。
 * @param <string>path 同$import 函数中的path。
 */
function ScriptLoadTask(path)
{
	this.init();
	this.addTask(function()
	{
		scriptLoader(this, path);
	});
}

Type.extend(ScriptLoadTask, TaskQueue);

/**
* 生成一个新的装载函数，绑定装载路径为：path
* @public
* @param <string>path 同$import 函数中的path。
*/
ScriptLoadTask.bind = function(path)
{
	return function()
	{
		loadScript(this, path);
	}
}

function loadScript(taskQueue, path)
{
	taskQueue.suspend();
	$import(path, function()
	{
		taskQueue.resume();
	}, null);
}

var dns = "http://www.xidea.org/taglib/decorator"
	, nsExp = /^[dD]\:/
	, typeAttr = "d:decorator";

if (System.isOpera(0, 8.9))
{
	typeAttr = "decorator";
}
//buildTree for firefox
function DecoratorEngine(decoratorProviders)
{
	this.providerMap = {};

	this.init();

	var tag = $JSI.getAttribute("decoratorTag");

	if (tag)
	{
		this.tagExp = new RegExp("^" + tag + "$", "i");
	}

	for (var i = decoratorProviders.length - 1; i >= 0; i--)
	{
		this.addTask(ScriptLoadTask.bind(decoratorProviders[i][0] + ":"));
	}

	this.addTask(function()
	{
		for (var i = 0; i < decoratorProviders.length; i++)
		{
			this.addProvider.apply(this, decoratorProviders[i]);
		}
		this.runDecorate();
	});
	//装饰完成后取消进度条
	this.addFinishListener(function ()
	{
		UIUtil.hideMask();
	});
}

var $p = Type.extend(DecoratorEngine, TaskQueue).prototype;

$p.runDecorate = function(ele)
{
	//var begin = new Date();
	var root = this.buildTree(ele || document.body);

	//$log.debug("搜索时间：" + (new Date() - begin) + "ms");
	if (root.length)
	{
		this.showStatus("准备装载资源...");

		for (var i = 0; i < root.length; i++)
		{
			root[i].parent = null;
			this.loadTree(root[i], this);
		}
	}
}
$p.showStatus = function(text)
{
	var content = UIUtil.showMask();
	content.innerHTML = "<div style='background:#7FFF00;line-height:16px;font-family: Verdana, Tahoma;padding:0 5px 0 11px;float:right;font-size:12px;'>"
		+ text + "</div>";
}

$p.loadTree = function(infoNode, taskQueue)
{
	var dc = null;
	var _this = this;
	var name = infoNode.name;
	var provider = this.getProvider(infoNode.attributes.get("provider"));
	var loader = provider.buildDecoratorLoader(name);

	if (loader)
	{
		taskQueue.addTask(createStatusFn(this, "装载装饰器: " + name));
		taskQueue.addTask(loader);
	}

	taskQueue.addTask(function()
	{
		dc = _this.buildDecorator(provider, infoNode);
		_this.beforeDecorate(dc, name);
	});

	for (var i = 0; i < infoNode.children.length; i++)
	{
		var subtask = new TaskQueue;
		taskQueue.addTask(subtask);
		_this.loadTree(infoNode.children[i], subtask);
	}
	taskQueue.addTask(createStatusFn(this, "运行装饰器: " + name))
	taskQueue.addTask(function()
	{
		_this.doDecorate(dc, name);
	});
}
$p.buildDecorator = function(provider, infoNode)
{
	var decorator = infoNode.decorator = provider.buildDecorator(infoNode.name);

	if (! decorator)
	{
		return $log.error("missed decorator: ", infoNode.name);
	}
	decorator.id = infoNode.id;

	if (infoNode.parent)
	{
		decorator.parent = infoNode.parent.decorator;
		(decorator.parent.children || (decorator.parent.children = [])).push(decorator);
	}

	infoNode.decorator = decorator;
	decorator.attributes = infoNode.attributes;
	return decorator;
}

$p.beforeDecorate = function(decorator, name)
{
	$JSI.addComponent(decorator);
	try
	{
		//注册decorator
		if(decorator.before)
			decorator.before();
	}
	catch (e)
	{
		$log.info("decorator.before error:", name, e);
	}
};
/**
 * 执行装饰处理
 * @public
 * @param <Decorator>decorator 装饰实例
 */
$p.doDecorate = function(decorator, name)
{
	try
	{
		decorator.decorate();
	}
	catch (e)
	{
		$log.info("decorator do decorate error:", name, e);
	}
};


/**
 * @public
 */
$p.addProvider = function(pkgname)
{
	var pkg = $import(pkgname + ":", null, null);
	var provider = new DecoratorProvider(pkg);
	this.providerMap[pkg.name] = provider;

	for (var i = arguments.length - 1; i >= 0; i--)
	{
		this.providerMap[arguments[i]] = provider;
	}
}
$p.getProvider = function(name)
{
	if (this.providerMap[name])
	{
		return this.providerMap[name];
	}
	else
	{
		return this.providerMap["*"];
	}
}

$p.buildTree = function(root)
{
	var tree =
	{
		children: []
	};

	if (this.tagExp)
	{
		this.htmlWalk(root, tree);
		return tree.children;
	}

	if (System.isIE())
	{
		var decoratorElements = root.all.urns(dns);
	//alert(decoratorElements.length + dns)
	}
	else if (document.documentElement.tagName == "html" //xhtml
	&& document.documentElement.namespaceURI == "http://www.w3.org/1999/xhtml")
	{
		var decoratorElements = document.getElementsByTagNameNS(dns, "*");
	}
	else
	{
		this.domWalk(root, tree);
		return tree.children;
	}
	var parentElements = [root];
	var parentNodeInfos = [tree];
	outer:

	for (var i = 0; i < decoratorElements.length; i++)
	{
		var e = decoratorElements[i];
		var infoNode = new NodeInfo(decoratorElements[i]);

		while (e = e.parentNode)
		{
			for (var j = parentNodeInfos.length - 1; j >= 0; j--)
			{
				if (parentElements[j] == e)
				{ //æ¾å°parent
					parentNodeInfos[j].children.push(infoNode);
					infoNode.parent = parentNodeInfos[j];
					parentNodeInfos[j + 1] = infoNode;
					parentElements[j + 1] = decoratorElements[i];
					parentElements.length = parentNodeInfos.length = j + 2;
					continue outer;
				}
			}
		}
	}

	delete decoratorElements;
	delete parentElements;
	delete parentNodeInfos;
	return tree.children;
}

$p.htmlWalk = function(htmlNode, parent)
{
	if (this.tagExp.test(htmlNode.tagName))
	{
		var dc = htmlNode.getAttribute(typeAttr);

		if (dc)
		{
			var info = new NodeInfo(htmlNode, true);
			parent.children.push(info);
			info.parent = parent;
			parent = info;
		}
	}

	var c = htmlNode.firstChild;

	while (c)
	{
		if (c.nodeType == 1)
		{
			this.htmlWalk(c, parent);
		}
		c = c.nextSibling;
	}
}

$p.domWalk = function(htmlNode, parent)
{
	if (/^[dD]\:/.test(htmlNode.tagName))
	{
		var name = htmlNode.tagName.substr(2);
		var info = new NodeInfo(htmlNode);
		parent.children.push(info);
		info.parent = parent;
		parent = info;
	}
	var c = htmlNode.firstChild;

	while (c)
	{
		if (c.nodeType == 1)
		{
			this.domWalk(c, parent);
		}
		c = c.nextSibling;
	}
}

/**
 * @private
 */
function DecoratorProvider(pkg)
{
	this.pkg = pkg;
	this.decoratorMap = {};

	this.init();
}
$p = DecoratorProvider.prototype;

$p.buildDecoratorLoader = function(name)
{
	//if(!name)alert(1111)
	name = name.toUpperCase();
	var obj = this.decoratorMap[name];

	if (obj)
	{
		if (typeof obj == "string")
		{
			//flag
			this.decoratorMap[name] = decoratorLoader;
			return decoratorLoader.bind(this, obj);
		}
	}
	return null;
}
$p.buildDecorator = function(name)
{
	name = name.toUpperCase();
	return new this.decoratorMap[name]();
}

$p.init = function()
{
	for (var name in this.pkg.objectScriptMap)
	{
		this.decoratorMap[name] = name;
		this.decoratorMap[name.toUpperCase()] = name;
	}
}
/**
 * @param name ;
 */
function decoratorLoader(taskQueue, provider, name)
{
	//if(!name)alert(555)
	var upperCaseName = name.toUpperCase();
	var obj = provider.decoratorMap[upperCaseName];

	if (obj == decoratorLoader)
	{
		taskQueue.suspend();
		$import(provider.pkg.name + ":" + name, function(cls)
		{
			provider.decoratorMap[upperCaseName] = cls;
			taskQueue.resume();
		}, null);
	}
}

decoratorLoader.bind = function(provider, name)
{
	return function()
	{
		decoratorLoader(this, provider, name);
	}
}

function NodeInfo(htmlNode, nsAttr)
{
	this.children = [];
	this.id = htmlNode.id || htmlNode.uniqueID || (htmlNode.id = System.GUID());

	if (nsAttr)
	{
		this.attributes = new Attributes(htmlNode, true);
		this.name = this.attributes.get("decorator");
	}
	else
	{
		this.attributes = new Attributes(htmlNode);
		this.name = htmlNode.tagName.replace(nsExp, "");
	}
}

function Attributes(htmlNode, ns)
{
	this.valueMap = {};
	if (System.isIE(0, 5.9))
	{
		this.initFromHTML(htmlNode, ns);
	}
	else
	{
		this.init(htmlNode, ns);
	}
}

$p = Attributes.prototype;

$p.initFromHTML = function(htmlNode, ns)
{
	var html = htmlNode.cloneNode().outerHTML;
	var matchs = html.match(/\b([\w\d_\$]+\s*=\s*("(\\.|[^\"\n\r])*"|'(\\.|[^\'\n\r])*'))/g);

	if (matchs)
	{
		for (var i = matchs.length - 1; i >= 0; i--)
		{
			var values = matchs[i].match(/([\w\d_\$]+)\s*=\s*['"](.*)["']$/);
			this.valueMap[values[1]] = values[2];
		}
	}
}

$p.init = function(htmlNode, ns)
{
	var attributes = htmlNode.attributes;

	for (var i = attributes.length - 1; i >= 0; i--)
	{
		var attribute = attributes[i];

		if (attribute && attribute.nodeValue)
		{
			var name = attribute.nodeName;

			if (ns)
			{
				if (nsExp.test(name))
				{
					name = name.substr(2);
				}
				else
				{
					continue;
				}
			}
			this.valueMap[name] = attribute.nodeValue;
		}
	}
}

$p.get = function(key)
{
	return this.valueMap[key] || this.valueMap[key.toUpperCase()];
}

function createStatusFn(o, s)
{
	return function()
	{
		o.showStatus(s);
	}
}

$p = null;